// Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.

// 可用会话比策略
package metric

import (
	"fmt"
	"math"

	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/influxdb"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/model"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

const (
	scaleInPercent = 0.5
)

type ServerSessions struct {
	TargetMaxPercent        float64
	TargetMinPercent        float64
	AvailablePercentOfGroup float64
	MaxNumOfGroup           int64
	UsedNumOfGroup          int64
	MaxNumOfInstance        int64
	StartTime               int64
	EndTime                 int64
}

func newServerSessionsWithTargetValue(metrics *influxdb.GroupServerSessionMetrics, targetMaxValue, targetMinValue int32) *ServerSessions {
	return &ServerSessions{
		TargetMaxPercent:        changePercentToFloat64(targetMaxValue),
		TargetMinPercent:        changePercentToFloat64(targetMinValue),
		AvailablePercentOfGroup: metrics.AvailablePercent,
		MaxNumOfGroup:           metrics.MaxNum,
		UsedNumOfGroup:          metrics.UsedNum,
		StartTime:               metrics.StartTime,
		EndTime:                 metrics.EndTime,
	}
}

func (s *ServerSessions) setMaxNumOfInstanceServerSessions(max int32) {
	s.MaxNumOfInstance = int64(max)
}

func (s *ServerSessions) getScalingOutNumber() float64 {
	maxNum := float64(s.MaxNumOfGroup)
	usedNum := float64(s.UsedNumOfGroup)
	maxNumOfInstance := float64(s.MaxNumOfInstance)
	return math.Ceil(twoDecimalPlaces(
		((s.TargetMinPercent-1)*maxNum + usedNum) / ((1 - s.TargetMinPercent) * maxNumOfInstance)))
}

func (s *ServerSessions) getScalingInNumber(curNum int32) float64 {
	if s.UsedNumOfGroup == 0 {
		return math.Floor(float64(curNum) * scaleInPercent)
	}
	maxNum := float64(s.MaxNumOfGroup)
	usedNum := float64(s.UsedNumOfGroup)
	maxNumOfInstance := float64(s.MaxNumOfInstance)
	return math.Floor(twoDecimalPlaces(
		((1-s.TargetMaxPercent)*maxNum - usedNum) / ((1 - s.TargetMaxPercent) * maxNumOfInstance)))
}

// ScalingDecisionByAvailableServerSessionsPercentOfGroup 根据AvailableServerSessionsPercent进行伸缩判断
func ScalingDecisionByAvailableServerSessionsPercentOfGroup(log *logger.FMLogger, influxCtr *influxdb.Controller,
	group *db.ScalingGroup, curActiveNum int32, metric *ServerSessions) (*model.ScalingDecision, error) {
	res := model.ScalingDecision{Action: model.ScalingDecisionActionNone}
	// 可用会话比 < 最小可用会话比 -> 扩容
	// 可用会话比 > 最大可用会话比 -> 缩容
	if metric.AvailablePercentOfGroup < metric.TargetMinPercent {
		res.CalculatedNum = metric.getScalingOutNumber()
		res.AvailableNum = float64(group.MaxInstanceNumber - curActiveNum)
		res.ScalingNum = math.Min(res.CalculatedNum, res.AvailableNum)
		if res.ScalingNum > 0 {
			res.Action = model.ScalingDecisionActionOut
			group.DesireInstanceNumber = int32(math.Min(float64(group.MaxInstanceNumber), float64(curActiveNum)+res.ScalingNum))
			log.Info("calculate group [%s] desire num:%d, current num:%d, will scale out", group.Id, group.DesireInstanceNumber, group.CurrentInstanceNumber)
		}
	} else if metric.AvailablePercentOfGroup > metric.TargetMaxPercent {
		res.CalculatedNum = metric.getScalingInNumber(curActiveNum)
		res.AvailableNum = float64(curActiveNum - group.MinInstanceNumber)
		res.ScalingNum = math.Min(res.CalculatedNum, res.AvailableNum)
		if res.ScalingNum > 0 {
			res.Action = model.ScalingDecisionActionIn
			res.Instances = influxCtr.GetTopUsedServerSessionOfInstance(log, group.Id, res.ScalingNum,
				metric.StartTime, metric.EndTime)
			// 为保持target = curNum - scaleInNum，target设为：激活+激活中 - 缩容数量
			group.DesireInstanceNumber = int32(math.Max(float64(group.MinInstanceNumber), float64(int(group.CurrentInstanceNumber)-len(res.Instances))))
			log.Info("calculate group [%s] desire num:%d, current num:%d, will scale in", group.Id, group.DesireInstanceNumber, group.CurrentInstanceNumber)
		}
	}
	log.Info("ScalingGroup[%s] auto scaling decision: %+v", group.Id, res)
	return &res, nil
}

func GetMetricData(log *logger.FMLogger, influxCtr *influxdb.Controller,
	group *db.ScalingGroup, targetMaxValue, targetMinValue int32) (*ServerSessions, error) {
	conf, err := db.GetInstanceConfigurationById(group.InstanceConfiguration.Id)
	if err != nil {
		return nil, fmt.Errorf("it's failed to get InstanceConfiguration[%s] of ScalingGroup[%s] from db, err: %s",
			group.InstanceConfiguration.Id, group.Id, err.Error())
	}

	groupMetrics, err := influxCtr.GetServerSessionMetricsOfScalingGroup(log, group.Id)
	if err != nil {
		return nil, fmt.Errorf("it's failed to get metric of ScalingGroup[%s],err: %s ", group.Id, err.Error())
	}
	if groupMetrics == nil {
		return nil, fmt.Errorf("get group [%s] metric data is nil", group.Id)
	}
	metric := newServerSessionsWithTargetValue(groupMetrics, targetMaxValue, targetMinValue)
	metric.setMaxNumOfInstanceServerSessions(conf.MaxServerSession)
	log.Info("ScalingGroup[%s] server session metrics: %+v", group.Id, *metric)
	return metric, nil
}
